package core

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"log"
	"strconv"
	"strings"
	"time"
)

// Blockchain 是一个简易的"区块链"结构体，区块链由多个区块构成
//
// # Blocks 是由区块组成的切片，代表着区块链中已经上链的区块
//
// PendingTransactions 是用户已经提交但是未上链的交易组成的切片，之所以未上链，是因为交易数量还不够，只有当交易数量达到每个区块最大可容纳交易数量 MaxTransactionAmountPerBlock 时，这些交易才会被一并打包成一个区块并上链
type Blockchain struct {
	Blocks                       []*Block
	PendingTransactions          []string
	Channel                      chan string
	MaxTransactionAmountPerBlock int
}

// NewBlockchain 会初始化一个 Blockchain 结构体并返回，该区块链每个区块的最大交易数量会被设置为传入的参数maxTransactionAmountPerBlock
//
// 函数执行的流程为：
//
// (1)生成一个 Blockchain 区块链实例，并将该实例的字段初始化
//
// (2)使用函数 GenerateGenesisBlock 生成一个创世区块
//
// (3)使用方法 AppendNewBlock 将创世区块添加到该区块链实例中
//
// (4)返回该区块链实例
func NewBlockchain(maxTransactionAmountPerBlock int) *Blockchain {
	blockchain := new(Blockchain)
	blockchain.Channel = make(chan string)
	blockchain.MaxTransactionAmountPerBlock = maxTransactionAmountPerBlock
	genesisBlock := GenerateGenesisBlock()
	blockchain.AppendNewBlock(genesisBlock)
	return blockchain
}

//NewAccount 会初始化一个Account结构体并返回
//
// (1) 生成一个Account实例
//
// (2) 初始化map
//
// (3) 返回实例

func NewAccount() *Accounts {
	myAccounts := new(Accounts)
	myAccounts.account = make(map[string]int)
	return myAccounts
}

// SendTransaction 会处理每个传入进来的交易，具体的处理流程为：
//
// (1)将交易添加到区块链的待上链交易中
//
// (2)判断待处理交易数量，如果数量大于前面设置的每个区块最大可容纳交易数量，则使用 GenerateNewBlock 函数生成一个新区块，再使用 AppendNewBlock 函数将该区块添加到区块链中，否则不作任何操作
//
//	或者如果等待时间超过x（30）秒，则无论有没有交易都上传一个区块链直到区块链个数达到六个
func (bc *Blockchain) SendTransaction(transaction string) {
	//bc.PendingTransactions = append(bc.PendingTransactions, transaction)
	//if len(bc.PendingTransactions) >= bc.MaxTransactionAmountPerBlock {
	//	bc.AppendNewBlock(GenerateNewBlock(bc.Blocks[len(bc.Blocks)-1], bc.PendingTransactions))
	//	bc.PendingTransactions = []string{}
	//}
	bc.Channel <- transaction
}

func (bc *Blockchain) CreateNewBlock() {
	intChan := make(chan int)
	//用两个协程分别表示计时间和等交易的输入
	//30秒倒计时
	go func() {
		timer1 := time.NewTimer(10 * time.Second)
		<-timer1.C
		intChan <- 1
	}()
	//不断读入，如果到了两个或者上面时间到了都结束
	go func() {
		i := 0
		for {
			s, ok := <-bc.Channel
			if ok != false {
				bc.PendingTransactions = append(bc.PendingTransactions, s)
				i++
			}
			//传入两个了就直接退出
			if i == 2 {
				intChan <- 2
				break
			}
		}
	}()
	for {
		_, ok := <-intChan
		if ok != false {
			bc.AppendNewBlock(GenerateNewBlock(bc.Blocks[len(bc.Blocks)-1], bc.PendingTransactions))
			bc.PendingTransactions = []string{}
			bc.PrintOneBlock(len(bc.Blocks) - 1)
			break
		}
	}
}

func (mA *Accounts) CompleteTransaction(transaction string) {
	splitString := strings.Split(transaction, " ")
	value, _ := strconv.ParseInt(splitString[2], 10, 64)
	//在这里处理账户的问题，如果map中间对应的名字已经存在，那么直接进行金额运算，如果不存在，则创建账户再进行金额转移
	if mA.account[splitString[0]] == 0 {
		hash := sha256.Sum256([]byte(splitString[0]))
		mA.account[splitString[0]] = len(mA.accounts)
		tempAccount := Account{Address: hex.EncodeToString(hash[0:8]), name: splitString[0], Balance: int64(100 - value)}
		mA.accounts = append(mA.accounts, tempAccount)
	} else {
		mA.accounts[mA.account[splitString[0]]].Balance -= value
	}
	if mA.account[splitString[1]] == 0 {
		hash := sha256.Sum256([]byte(splitString[1]))
		mA.account[splitString[1]] = len(mA.accounts)
		tempAccount := Account{Address: hex.EncodeToString(hash[0:8]), name: splitString[1], Balance: int64(100 + value)}
		mA.accounts = append(mA.accounts, tempAccount)
	} else {
		mA.accounts[mA.account[splitString[1]]].Balance += value
	}

}

// AppendNewBlock 会将传入的新区块添加到区块链中，具体的处理流程为：
//
// (1)如果区块链中目前没有区块，则直接将新区块（也即创世区块）添加到区块链中
//
// (2)如果区块链中目前已经有区块，则需要将新区块传入 CheckValid 函数，判断新区块是否合法（合法的定义可见 CheckValid 函数注释），若合法，则将新区块添加到区块链中，否则向终端报错并终止程序运行
func (bc *Blockchain) AppendNewBlock(newBlock *Block) {
	if len(bc.Blocks) == 0 {
		bc.Blocks = append(bc.Blocks, newBlock)
	} else {
		if bc.CheckValid(newBlock) {
			bc.Blocks = append(bc.Blocks, newBlock)
		} else {
			log.Fatal("invalid new block")
		}
	}
}

// CheckValid 返回新区块的合法性
//
// 新区块只有同时满足以下条件时才是合法的：
//
// (1)新区块的序号 Index 等于前一区块的序号值加一
//
// (2)新区块的 PreviousHash 字段值等于前一区块的 Hash 字段值
//
// (3)新区块使用 CalculateHash 函数计算出来的哈希结果等于新区块的 Hash 字段值
func (bc *Blockchain) CheckValid(newBlock *Block) bool {
	oldBlock := bc.Blocks[len(bc.Blocks)-1]
	if newBlock.Index-1 != oldBlock.Index {
		return false
	}
	if newBlock.PreviousHash != oldBlock.Hash {
		return false
	}
	if CalculateHash(newBlock) != newBlock.Hash {
		return false
	}
	return true
}

// Print 可以打印出来区块链的所有历史
func (bc *Blockchain) Print() {
	length := len(bc.Blocks)
	for i := 0; i < length; i++ {
		fmt.Println("Block Index", i)
		fmt.Printf("\t-timestamp:%d\n", bc.Blocks[i].TimeStamp)
		fmt.Printf("\t-previous hash:%v\n", bc.Blocks[i].PreviousHash)
		fmt.Printf("\t-hash:%v\n", bc.Blocks[i].Hash)
		fmt.Printf("\t-transactions:\n")
		temp := len(bc.Blocks[i].Transaction.From)
		if temp == 0 {
			fmt.Printf("\t\t--block %d : no transaction!\n", i)
		} else if temp == 1 {
			if bc.Blocks[i].Transaction.From[0] == "nil" {
				fmt.Printf("\t\t--transaction %d of block %d : ***genesis block***\n", 0, 0)
			} else {
				fmt.Printf("\t\t--transaction %d of block %d : %s\n", 0, i, bc.Blocks[i].Transaction.From[0]+" gave "+bc.Blocks[i].Transaction.To[0]+" "+strconv.FormatInt(bc.Blocks[i].Transaction.Value[0], 10)+" dollars.")
			}
		} else {
			for j := 0; j <= 1; j++ {
				fmt.Printf("\t\t--transaction %d of block %d : %s\n", j, i, bc.Blocks[i].Transaction.From[j]+" gave "+bc.Blocks[i].Transaction.To[j]+" "+strconv.FormatInt(bc.Blocks[i].Transaction.Value[j], 10)+" dollars.")
			}
		}
	}
}

// PrintList 可以打印出来所有的账户信息
func (mA *Accounts) PrintList() {
	for i := 2; i < len(mA.accounts); i++ {
		fmt.Printf("Index : %d , name : %s , Address : %s , Balance : %d\n", i-2, mA.accounts[i].name, mA.accounts[i].Address, mA.accounts[i].Balance)
	}
}

// PrintOneBlock 可以打印出来一个单一的block
func (bc *Blockchain) PrintOneBlock(i int) {
	fmt.Println("Block Index", i)
	fmt.Printf("\t-timestamp:%d\n", bc.Blocks[i].TimeStamp)
	fmt.Printf("\t-previous hash:%v\n", bc.Blocks[i].PreviousHash)
	fmt.Printf("\t-hash:%v\n", bc.Blocks[i].Hash)
	fmt.Printf("\t-transactions:\n")
	temp := len(bc.Blocks[i].Transaction.From)
	if temp == 0 {
		fmt.Printf("\t\t--block %d : no transaction!\n", i)
	} else if temp == 1 {
		fmt.Printf("\t\t--transaction %d of block %d : %s\n", 0, i, bc.Blocks[i].Transaction.From[0]+" gave "+bc.Blocks[i].Transaction.To[0]+" "+strconv.FormatInt(bc.Blocks[i].Transaction.Value[0], 10)+" dollars.")
	} else {
		for j := 0; j <= 1; j++ {
			fmt.Printf("\t\t--transaction %d of block %d : %s\n", j, i, bc.Blocks[i].Transaction.From[j]+" gave "+bc.Blocks[i].Transaction.To[j]+" "+strconv.FormatInt(bc.Blocks[i].Transaction.Value[j], 10)+" dollars.")
		}
	}
}
